<?php
namespace core;


use config\Mapping;
use core\controller\Controller;
use core\filter\Filter;
use core\library\Input;
use core\library\Log;
use core\utils\Bean;
use core\utils\BeanManager;
use Error;
use Exception;
use ReflectionClass;
use ReflectionMethod;

class Application implements Bean
{
    private $router;
    private static $instance;

    /**
     * 这个类的构造方法，负责整个工程的启动
     * Application constructor.
     * @throws \ReflectionException
     */
    private function __construct() {
        // 托管输出
        ob_start();

        // 注册自身
        BeanManager::getInstance()->register(__CLASS__, $this);
        // 解析路由
        $this->router = $this->parseRouter();
        // 控制器文件
        $controllerFile = 'modules/'.$this->router['module'].'/controller/'.ucfirst($this->router['controller']).'Controller.php';
        if (file_exists($controllerFile)) {
            require $controllerFile;

            // 控制器类全路径
            $class = 'modules\\'.$this->router['module'].'\\controller\\'.ucfirst($this->router['controller']).'Controller';
            // 获得控制器类的反射
            $ref = new ReflectionClass($class);
            $obj = $ref->newInstance();


            // 这一段用来读取类头部声明的所有 use 的类， 并且放到一个数组里面
            $content = explode("\n", file_get_contents($controllerFile));
            $flag = false;
            $usingClass = [];
            foreach ($content as $row) {
                $u = sscanf($row, "%s%s");
                if ($flag == true && isset($u[0]) && 'use' != $u[0])
                    break;

                if (isset($u[0]) && 'use' === $u[0]) {
                    $flag = true;
                    $u[1] = rtrim($u[1], ';');
                    $usingClass[substr($u[1], strrpos($u[1], '\\') + 1)] = $u[1];
                }
            }
            // 获取Controller的所有属性
            $properties = $ref->getProperties();
            // 注入属性
            foreach ($properties as $property) {
                // 获取属性的类型(不包括包路径)
                $type = $this->getVarTypeDeclare($property);
                // 获得属性的类型(包括包路径)
                if (!isset($usingClass[$type]))
                    continue;
                $trueParamType = $usingClass[$type];
                // 注入属性
                $property->setAccessible(true);
                $property->setValue($obj, BeanManager::getInstance()->getBean($trueParamType));
            }

            // 检验映射的方法是否存在
            $actionName = 'action'.ucfirst($this->router['action']);
            if (!$ref->hasMethod($actionName)) {
                http_response_code(404);
                $log = Log::getInstance();
                $log->error();
                (new Controller())->coreRedirect('404');
                exit(1);
            }
            // 获得要执行反射的方法
            $method = $ref->getMethod($actionName);
            if ($method->isPublic()) {
                // 解析要传入的参数
                $args = $this->parseParameters($method);
                // 获得命中的过滤器
                $filters = $this->parseFilters();
                // 启动
                $this->exec($method, $obj, $filters, $args);
            } else { // TODO 这里应该是403没有权限访问
                http_response_code(404);
                $log = Log::getInstance();
                $log->error();
                // 无法找到方法或者方法不是public的方法
                (new Controller())->coreRedirect('404');
            }
        } else {
            // 无法找到类
            http_response_code(404);
            $log = Log::getInstance();
            $log->error();
            (new Controller())->coreRedirect('404');
        }
    }

    /**
     * 从注释中获取一个属性的类型, 通过@Autowire来判断是否需要自动注入
     * @param \ReflectionProperty $property
     * @return string 如果需要自动注入，那就返回其类型，否则返回空字符串
     */
    private function getVarTypeDeclare(\ReflectionProperty $property) : string {
        $comment = $property->getDocComment();
        if (false === $comment) {
            return '';
        } else {
            $pos = strpos($comment, '@Autowire');
            if (false === $pos)
                return '';
            $pos = strpos($comment, '@var');
            if (false === $pos)
                return '';

            $str = substr($comment,  $pos + strlen('@var') + 1);
            sscanf($str, '%s', $type);
            return $type;
        }
    }

    /**
     * 通过反射执行方法
     * @param ReflectionMethod $method 要执行的方法
     * @param object $class 类的实例
     * @param Filter[] $filters 击中该方法的Filter
     * @param array $args 方法的参数
     * @throws Exception
     */
    private function exec(ReflectionMethod $method, $class, $filters, array $args) {
        $filterNum = count($filters);
        // 执行方法
        try {
            // 执行Filters的before
            for ($i = 0; $i < $filterNum; ++ $i) {
                $filters[$i]->before();
            }
            // 执行啊
            $rev = $method->invokeArgs($class, $args);

            // Filters的afterReturning
            for ($i = $filterNum - 1; $i >= 0; -- $i) {
                $filters[$i]->afterReturning();
            }
            // 如果在方法里面使用return，那就输出到页面上
            if (is_array($rev) || is_object($rev)) {
                echo json_encode($rev, JSON_UNESCAPED_UNICODE);
            } else {
                echo $rev;
            }

        } catch (Exception $exception) {
            for ($i = $filterNum - 1; $i >= 0; -- $i) {
                // Filters的afterThrowing
                $filters[$i]->afterThrowing();
            }

            http_response_code(500);
            // 抛出异常的时候清除输出缓冲区
            ob_clean();
            throw $exception;
        } finally {
            // Filters的after
            for ($i = $filterNum - 1; $i >= 0; -- $i) {
                $filters[$i]->after();
            }

            // 记录日志
            $log = Log::getInstance();
            if (http_response_code() >= 400)
                $log->error();
            else
                $log->access();
        }
    }

    /**
     * 解析方法的参数，并且把解析后的结果返回出去
     * @param ReflectionMethod $method   要解析的方法
     * @return array
     * @throws \ReflectionException
     */
    private function parseParameters(ReflectionMethod $method) : array {
        $input = Input::getInstance();
        // 获得方法形参数组
        $parameters = $method->getParameters();
        // 实参数组
        $args = [];
        // 解析方法参数
        // 并且进行参数传值
        foreach ($parameters as $key=>$parameter) {
            $paramClass = $parameter->getClass();
            if (null == $paramClass) { // string或者int或者array
                // 解析参数类型
                if ($parameter->hasType()) {
                    $type = $parameter->getType()->getName();
                } else { // 没有声明类型的
                    $type = 'string';
                }
                // 尝试从$_GET和$_POST中获取参数
                // 如果$_GET和$_POST都含有指定的key，那么就会从$_POST中获取，而忽略$_GET中的参数
                if (null !== $input->pathVariable($parameter->getName())) {
                    $args[$key] = $input->pathVariable($parameter->getName());
                } else if (null !== $input->post($parameter->getName())) {
                    $args[$key] = $input->post($parameter->getName());
                } else if (null !== $input->get($parameter->getName())) {
                    $args[$key] = $input->get($parameter->getName());
                }
                // 没有获取到参数或者参数类型不正确，并且参数有默认值
                if ( ((!isset($args[$key])) || ($type === 'int' && !is_numeric($args[$key])))
                    && $parameter->isDefaultValueAvailable()) {
                    $args[$key] = $parameter->getDefaultValue();
                }
                // 默认值都解决不了的问题
                if (!isset($args[$key]) || ($type === 'int' && !is_numeric($args[$key]))) {
                    throw new Error('无法匹配参数'.$parameter->getName());
                }
            } else { // class 类型
                $manager = BeanManager::getInstance();
                $args[$key] = $manager->getBean($paramClass->getName());
                if (false === $args[$key]) {
                    throw new Error('参数类型不是一个合法的bean');
                }
            }
        }
        return $args;
    }

    /**
     * 获取所有命中该请求的Filter
     * @return Filter[] 解析之后的Filter
     * @throws \ReflectionException
     */
    private function parseFilters() {
        // 获取所有定义的Filters(文件名数组)
        $filterFiles = $this->readAllDeclareFilters();
        // 命中的Filters
        $matchFilters = [];
        foreach ($filterFiles as $file) {
            // 实例化Filter数组
            $filterClass = str_replace('/', '\\', str_replace('.php', '', $file));
            $filter = BeanManager::getInstance()->getBean($filterClass);
            // 是Filter类的之类并且定义命中请求
            if ($filter instanceof Filter &&
                $filter->match($this->router['module'], $this->router['controller'], $this->router['action'])) {
                $matchFilters[] = $filter;
            }
        }
        // 根据Filter的sort字段对Filters排序
        usort($matchFilters, function (Filter $a, Filter $b) { return $a->getSort() - $b->getSort();});

        return $matchFilters;
    }

    /**
     * 读取所有的定义的Filter
     * @return array
     */
    private function readAllDeclareFilters() {
        $filters = [];
        $filterDir = config('filter.dir');
        foreach ($filterDir as $dir) {
            if (is_dir($dir)) {
                $filters += $this->getFiltersRecursion($dir);
            } else {
                throw new Error('无法找到filter对应的文件夹');
            }
        }
        return $filters;
    }

    /**
     * 递归的读取目录下的所有Filter
     * @param $dir
     * @return array
     */
    private function getFiltersRecursion(string $dir) : array {
        $handler = opendir($dir);
        $rv = [];
        while ($file = readdir($handler)) {
            if ('.' == $file || '..' == $file) { // 忽略当前和父目录
                continue;
            } else if (is_dir($dir . '/' . $file)) { // 如果是路径那就递归读取
                $rv += $this->getFiltersRecursion($dir . '/' . $file);
            } else { // 如果是文件名并且以php作为最终后缀的
                if (substr($file, strrpos($file, '.') + 1) == 'php')
                    $rv[] = $dir . '/' . $file;
            }
        }
        return $rv;
    }

    /**
     * 启动应用程序，真正的启动逻辑在构造器里面
     * @return Application
     * @throws \ReflectionException
     */
    public static function start() {
        if (!self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * 解析URL
     * @return array
     * @throws \ReflectionException
     */
    public function parseRouter()
    {
        $input = Input::getInstance();
        $url = $input->get('s') ?? '';
        $mapping = BeanManager::getInstance()->getBean(Mapping::class);
        // 获取映射
        $url = $mapping->getTrueUrl($url, $this->getMethod());
        // 请求的方式被Mapping禁止
        if (false == $url) {
            http_response_code(405);
            $log = Log::getInstance();
            $log->error();
            exit();
        }
        $trueRouter = [
            'module' => config('router.default.module'),
            'controller' => config('router.default.controller'),
            'action' => config('router.default.action')
        ];

        $uri = trim($_SERVER['REQUEST_URI'], '/');
        if (preg_match('/^(\w+)\.php\?{0,1}/', $uri, $match)) {
            $trueRouter['module'] = $match[1];
            preg_match('/s=([a-zA-Z%\/]+)&{0,1}/', $uri, $match);
            if (count($match) > 1)
                $url = $match[1];
            else
                $url = '';
        }

        // 去掉开头的 /
        $router = explode('/', trim($url, '/'));

        switch (count($router)) {
            case 0:
                break;
            case 1:
                if ('' !== $router[0])
                    $trueRouter['controller'] = $router[0];
                break;
            case 2:
                $trueRouter['controller'] = $router[0];
                $trueRouter['action'] = $router[1];
                break;
            default: // >=3的时候都是只取前三个
                $trueRouter['module'] = $router[0];
                $trueRouter['controller'] = $router[1];
                $trueRouter['action'] = $router[2];
        }


        return $trueRouter;
    }

    /**
     * 获取所有的模块
     * 默认modules文件夹下面的一个文件夹就是一个模块
     * @return array
     */
    public function getModules() {
        static $modules;
        if (!$modules) {
            $modulesDir = './modules';
            $handler = opendir($modulesDir);
            while ($dir = readdir($handler)) {
                $realDir = $modulesDir . '/' . $dir;
                if (is_dir($realDir) && '.' !== $dir && '..' !== $dir) {
                    $modules[] = $dir;
                }
            }
        }
        return $modules;
    }

    /**
     * 获取当前请求属于哪个模块
     * @return string
     */
    public function getModule()  {
        return isset($this->router['module']) ? $this->router['module'] : '';
    }

    /**
     * 获得请求的控制器（不包括 Controller后缀）
     * @return string
     */
    public function getController() : string {
        return $this->router['controller'];
    }

    /**
     * 获得请求的方法(不包括请求的前缀action)
     * @return string
     */
    public function getAction() : string {
        return $this->router['action'];
    }

    /**
     * 获得请求的方法 (GET|POST)
     * @return string
     */
    public function getMethod() : string {
        if ($_SERVER['REQUEST_METHOD'])
            return $_SERVER['REQUEST_METHOD'];
        return 'GET';
    }

    /**
     * 获得请求的方式 (http/1.1)
     * @return string
     */
    public function getProtocol() {
        return $_SERVER['SERVER_PROTOCOL'];
    }

    /**
     * 获取请求的host
     * @return string
     */
    public function getHost() {
        return $_SERVER['HTTP_HOST'];
    }

    /**
     * 获取请求的端口
     * @return string
     */
    public function getPort() {
        return $_SERVER['SERVER_PORT'];
    }

    public function getRequestScheme() {
        return $_SERVER['REQUEST_SCHEME'];
    }
}